// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;

namespace SnapObjects.Data.Oracle
{
    internal class OracleSqlSelectStatementGenerator : SqlSelectStatementGenerator
    {
        private const string ROWNUMBERALIAS = "P_P_H_S_ROW_NUMBER_P_P_H_E";
        private const string TABLEALIAS = "P_P_H_S_TABLE_ALIAS_P_P_H_E";

        public OracleSqlSelectStatementGenerator(
            ISqlStatementFactory factory,
            ISqlGenerationHelper helper,
            ITypeMapper typeMapper)
            : base(factory, helper, typeMapper)
        {

        }

        protected override IReadOnlyList<SqlToken> SpliceStatement(SqlStatement statement)
        {
            var result = new List<SqlToken>();

            var select = (SqlSelectStatement)statement;

            this.CreateSelectKeyword(result, select);
            this.CreateSelectColumns(result, select);
            this.CreateFrom(result, select);
            this.CreateWhere(result, select);

            if (select.Skip == -1 && select.Take == -1)
            {
                this.CreateGroupBy(result, select);
                this.CreateHaving(result, select);
                this.CreateUnions(result, select);
                this.CreateOrderBy(result, select);
            }

            return result;
        }

        protected override void CreateSelectKeyword(List<SqlToken> tokens, SqlSelectStatement select)
        {
            tokens.Add(new SqlKeywordToken(SqlKeywords.SELECT));

            tokens.Add(SqlWhitespaceToken.Whitespace);

            if (select.IsDistinst)
            {
                tokens.Add(new SqlKeywordToken(SqlKeywords.DISTINCT));

                tokens.Add(SqlWhitespaceToken.Whitespace);
            }

            if (select.Take > -1)
            {
                var hasColumns = this.InternalCreateSelectColumns(tokens, select);

                if (hasColumns)
                {
                    tokens.Add(SqlWhitespaceToken.Whitespace);
                    tokens.Add(new SqlKeywordToken(SqlKeywords.FROM));
                    tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));

                    base.CreateSelectKeyword(tokens, select);
                }
            }
        }

        private bool InternalCreateSelectColumns(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.Select.Count > 0)
            {
                SqlToken preToken = null;

                foreach (var token in select.Select)
                {
                    if (token == null)
                    {
                        continue;
                    }

                    if (!String.IsNullOrWhiteSpace(token.Text) && token.Text.Equals(","))
                    {
                        tokens.Add(preToken);
                        tokens.Add(token);
                    }
                    else if (!String.IsNullOrWhiteSpace(token.Text))
                    {
                        preToken = token;
                    }
                }

                if (preToken != null)
                {
                    tokens.Add(preToken);
                }

                return true;
            }

            return false;
        }

        protected override bool CreateSelectColumns(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.Skip > -1 || select.Take > -1)
            {
                var hasColumns = this.InternalCreateSelectColumns(tokens, select);

                tokens.Add(new SqlCharacterToken(SqlCharacters.Comma));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlFunctionToken("ROW_NUMBER()"));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(OracleSqlKeywords.OVER));
                tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));

                tokens.Add(new SqlKeywordToken(SqlKeywords.ORDER));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.BY));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlNumericToken("1"));

                tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.AS));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlOtherToken(ROWNUMBERALIAS));

                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.FROM));
                tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));

                base.CreateSelectKeyword(tokens, select);
            }

            return base.CreateSelectColumns(tokens, select);
        }

        protected override bool CreateWhere(List<SqlToken> tokens, SqlSelectStatement select)
        {
            var result = base.CreateWhere(tokens, select);

            if (select.Skip > -1 || select.Take > -1)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);

                this.CreateGroupBy(tokens, select);
                this.CreateHaving(tokens, select);
                this.CreateUnions(tokens, select);

                if (select.Unions.Count < 1)
                {
                    base.CreateOrderBy(tokens, select);
                }

                tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));

                tokens.Add(SqlWhitespaceToken.Whitespace);

                if (select.Unions.Count > 0)
                {
                    base.CreateOrderBy(tokens, select);
                }

                tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));

                tokens.Add(SqlWhitespaceToken.Whitespace);

                tokens.Add(new SqlIdentifierToken(TABLEALIAS));

                tokens.Add(SqlWhitespaceToken.Whitespace);

                tokens.Add(new SqlKeywordToken(SqlKeywords.WHERE));

                tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));

                if (select.Skip > 0)
                {
                    tokens.Add(SqlWhitespaceToken.Whitespace);
                    tokens.Add(new SqlOtherToken(ROWNUMBERALIAS));
                    tokens.Add(new SqlOperatorToken(SqlOperator.GreaterThan));
                    tokens.Add(new SqlNumericToken(Math.Abs(select.Skip - select.Take).ToString()));
                    tokens.Add(SqlWhitespaceToken.Whitespace);

                    tokens.Add(new SqlKeywordToken(SqlKeywords.AND));

                    tokens.Add(SqlWhitespaceToken.Whitespace);
                    tokens.Add(new SqlOtherToken(ROWNUMBERALIAS));
                    tokens.Add(new SqlOperatorToken(SqlOperator.LessThanOrEquals));
                    tokens.Add(new SqlNumericToken(select.Skip.ToString()));
                }
                else if (select.Take > 0)
                {
                    tokens.Add(SqlWhitespaceToken.Whitespace);
                    tokens.Add(new SqlOtherToken(ROWNUMBERALIAS));
                    tokens.Add(new SqlOperatorToken(SqlOperator.LessThanOrEquals));
                    tokens.Add(new SqlNumericToken(select.Take.ToString()));
                }

                tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));
            }

            return result;
        }
    }
}
